#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ntsecapi.h>
#include <stdlib.h>
#include <time.h>
#include <wchar.h>
#include <lm.h>
#include <stdio.h>

#include "pginstca.h"

/* From ntstatus.h that is missing in mingw */
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0
#endif
#ifndef STATUS_NO_MORE_ENTRIES
#define STATUS_NO_MORE_ENTRIES           ((NTSTATUS)0x8000001AL)
#endif

#ifndef _MSC_VER
/* From winnt.h that is missing in mingw */
typedef enum {

    WinNullSid                                  = 0,
    WinWorldSid                                 = 1,
    WinLocalSid                                 = 2,
    WinCreatorOwnerSid                          = 3,
    WinCreatorGroupSid                          = 4,
    WinCreatorOwnerServerSid                    = 5,
    WinCreatorGroupServerSid                    = 6,
    WinNtAuthoritySid                           = 7,
    WinDialupSid                                = 8,
    WinNetworkSid                               = 9,
    WinBatchSid                                 = 10,
    WinInteractiveSid                           = 11,
    WinServiceSid                               = 12,
    WinAnonymousSid                             = 13,
    WinProxySid                                 = 14,
    WinEnterpriseControllersSid                 = 15,
    WinSelfSid                                  = 16,
    WinAuthenticatedUserSid                     = 17,
    WinRestrictedCodeSid                        = 18,
    WinTerminalServerSid                        = 19,
    WinRemoteLogonIdSid                         = 20,
    WinLogonIdsSid                              = 21,
    WinLocalSystemSid                           = 22,
    WinLocalServiceSid                          = 23,
    WinNetworkServiceSid                        = 24,
    WinBuiltinDomainSid                         = 25,
    WinBuiltinAdministratorsSid                 = 26,
    WinBuiltinUsersSid                          = 27,
    WinBuiltinGuestsSid                         = 28,
    WinBuiltinPowerUsersSid                     = 29,
    WinBuiltinAccountOperatorsSid               = 30,
    WinBuiltinSystemOperatorsSid                = 31,
    WinBuiltinPrintOperatorsSid                 = 32,
    WinBuiltinBackupOperatorsSid                = 33,
    WinBuiltinReplicatorSid                     = 34,
    WinBuiltinPreWindows2000CompatibleAccessSid = 35,
    WinBuiltinRemoteDesktopUsersSid             = 36,
    WinBuiltinNetworkConfigurationOperatorsSid  = 37,
    WinAccountAdministratorSid                  = 38,
    WinAccountGuestSid                          = 39,
    WinAccountKrbtgtSid                         = 40,
    WinAccountDomainAdminsSid                   = 41,
    WinAccountDomainUsersSid                    = 42,
    WinAccountDomainGuestsSid                   = 43,
    WinAccountComputersSid                      = 44,
    WinAccountControllersSid                    = 45,
    WinAccountCertAdminsSid                     = 46,
    WinAccountSchemaAdminsSid                   = 47,
    WinAccountEnterpriseAdminsSid               = 48,
    WinAccountPolicyAdminsSid                   = 49,
    WinAccountRasAndIasServersSid               = 50,
    WinNTLMAuthenticationSid                    = 51,
    WinDigestAuthenticationSid                  = 52,
    WinSChannelAuthenticationSid                = 53,
    WinThisOrganizationSid                      = 54,
    WinOtherOrganizationSid                     = 55,
    WinBuiltinIncomingForestTrustBuildersSid    = 56,
    WinBuiltinPerfMonitoringUsersSid            = 57,
    WinBuiltinPerfLoggingUsersSid               = 58,

} WELL_KNOWN_SID_TYPE;
#endif

#define SID_MAX_SUB_AUTHORITIES          (15)
#define SID_RECOMMENDED_SUB_AUTHORITIES  (1)    // Will change to around 6
#define SECURITY_MAX_SID_SIZE  \
      (sizeof(SID) - sizeof(DWORD) + (SID_MAX_SUB_AUTHORITIES * sizeof(DWORD)))

static void
InitLsaString(
    PLSA_UNICODE_STRING LsaString,
	const char *string
    )
{
    DWORD StringLength;

    if (string == NULL) {
        LsaString->Buffer = NULL;
        LsaString->Length = 0;
        LsaString->MaximumLength = 0;
        return;
    }

    StringLength = strlen(string);
    LsaString->Buffer = malloc((StringLength+1) * sizeof(WCHAR));
	_snwprintf(LsaString->Buffer,StringLength,L"%S",string);
    LsaString->Length = (USHORT) StringLength * sizeof(WCHAR);
    LsaString->MaximumLength=(USHORT)(StringLength+1) * sizeof(WCHAR);
}

BOOL CheckUserExists(LPTSTR szDomain, LPTSTR szUser)
{
	char uname[1024];
	DWORD refsize = 0;
	SID_NAME_USE peUse;
	DWORD sidsize = 0;

	wsprintf(uname,"%s\\%s",szDomain,szUser);
	
	if (LookupAccountName(NULL,
						  uname,
						  NULL,
						  &sidsize,
						  NULL,
						  &refsize,
						  &peUse))
		return TRUE;
	if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		/* account existed but buffer too small - that's expected! */
		return TRUE;
	return FALSE;
}

BOOL CreateUser(LPTSTR szDomain, LPTSTR szUser, LPTSTR szPassword, WCHAR *errbuf, DWORD errsize)
{
	USER_INFO_1 ui;
	DWORD dwLevel = 1;
	DWORD dwError = 0;
	NET_API_STATUS nStatus;
	int nLen;
	char szComment[] = "PostgreSQL service account";
	WCHAR wszUser[100], wszPassword[100], wszComment[100];
	
	nLen = MultiByteToWideChar(CP_ACP, 0, szUser, -1, NULL, (int)NULL);
	MultiByteToWideChar(CP_ACP, 0, 	szUser, -1, wszUser, nLen);
	
	nLen = MultiByteToWideChar(CP_ACP, 0, szPassword, -1, NULL, (int)NULL);
	MultiByteToWideChar(CP_ACP, 0, 	szPassword, -1, wszPassword, nLen);

	nLen = MultiByteToWideChar(CP_ACP, 0, szComment, -1, NULL, (int)NULL);
	MultiByteToWideChar(CP_ACP, 0, 	szComment, -1, wszComment, nLen);
	
	ui.usri1_name = wszUser;
	ui.usri1_password = wszPassword;
	ui.usri1_priv = USER_PRIV_USER;
	ui.usri1_home_dir = NULL;
	ui.usri1_comment = wszComment;
	ui.usri1_flags = UF_SCRIPT | UF_DONT_EXPIRE_PASSWD | UF_PASSWD_CANT_CHANGE;
	ui.usri1_script_path = NULL;

	nStatus = NetUserAdd(NULL,
						 dwLevel,
						 (LPBYTE)&ui,
						 &dwError);
	
	if (nStatus != NERR_Success)
	{
		WCHAR *accterr;
		switch (nStatus)
		{
			case ERROR_ACCESS_DENIED: accterr=__("Access Denied");break;
			case NERR_InvalidComputer: accterr=__("The computer name is invalid");break;
			case NERR_NotPrimary: accterr=__("The operation is allowed only on the primary domain controller of the domain.");break;
			case NERR_GroupExists: accterr=__("The group already exists.");break;
			case NERR_UserExists: accterr=__("The user account already exists.");break;
			case NERR_PasswordTooShort: accterr=__("The password is too short or not complex enough.");break;
			default: accterr=__("Unknown error");break;
		}
		_snwprintf(errbuf, errsize, __("User '%S' could not be created: %s!"), szUser, accterr);

		return FALSE;
	}

	return TRUE;
}


static struct _privInfo {
	char *privname;
	WCHAR *friendlyname;
	BOOL has;
} _privileges[3] = {
	{ SE_SERVICE_LOGON_NAME, L"Log on as a service", FALSE },
	{ SE_NETWORK_LOGON_NAME, L"Access this computer from the network", FALSE },
	{ SE_INTERACTIVE_LOGON_NAME, L"Log on locally", FALSE}
};


static void CheckDefaultPrivileges(LSA_HANDLE PolicyHandle)
{
	int i;
	PSID defaultSids[4];
	SID_IDENTIFIER_AUTHORITY WorldAuthority = { SECURITY_WORLD_SID_AUTHORITY };
	SID_IDENTIFIER_AUTHORITY NtAuthority = { SECURITY_NT_AUTHORITY };

	if (!AllocateAndInitializeSid(&WorldAuthority, 1, SECURITY_WORLD_RID, 0, 0,0,0,0,0,0, &defaultSids[0]))
		defaultSids[0]=NULL;
	if (!AllocateAndInitializeSid(&NtAuthority, 1, SECURITY_AUTHENTICATED_USER_RID, 0,0,0,0,0,0,0,&defaultSids[1]))
		defaultSids[1]=NULL;
	if (!AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_GROUP_RID_USERS, 0,0,0,0,0,0, &defaultSids[2]))
		defaultSids[2]=NULL;
	if (!AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS, 0,0,0,0,0,0, &defaultSids[3]))
		defaultSids[3]=NULL;


	for (i=0;i<sizeof(_privileges)/sizeof(_privileges[0]);i++)
	{
		LSA_UNICODE_STRING priv;
		NTSTATUS status;
		LSA_ENUMERATION_INFORMATION *info = NULL;
		ULONG count;
		int j;

		InitLsaString(&priv, _privileges[i].privname);

		status = LsaEnumerateAccountsWithUserRight(PolicyHandle, &priv, (LPVOID *)&info, &count);
		if (status == STATUS_SUCCESS)
		{
			for (j=0; j < count; j++)
			{
				int k;
				for (k=0; k < sizeof(defaultSids)/sizeof(defaultSids[0]); k++)
				{
					if (EqualSid(info[j].Sid, defaultSids[k]))
					{
						_privileges[i].has=TRUE;
						break;
					}
				}
				if (_privileges[i].has)
					break;
			}
			LsaFreeMemory(info);
		}
		/* as we don't abort anyway, let's ignore telling people about it because it makes the code simpler :-)
		else
			if (status != STATUS_NO_MORE_ENTRIES)
				MsgBox(0,NULL,__("Failed to enumerate user accounts for %s (%i)"),_privileges[i].friendlyname,(int)LsaNtStatusToWinError(status));
				*/
	}
	for (i=0; i < sizeof(defaultSids)/sizeof(defaultSids[0]); i++)
		FreeSid(defaultSids[i]);
}

BOOL CheckUserPrivileges(LPTSTR szDomain, LPTSTR szUser, BOOL noprompt, WCHAR *errbuf, DWORD errsize)
{
	char uname[1024];
	SID_NAME_USE peUse;
	PSID sid;
	DWORD sidsize = sizeof(sid);
	char refdom[MAX_PATH];
	DWORD refsize = sizeof(refdom);

	LSA_OBJECT_ATTRIBUTES ObjectAttributes;
	LSA_HANDLE PolicyHandle;
	NTSTATUS status;
	PLSA_UNICODE_STRING rightList=NULL;
	ULONG rightCount =0;
	ULONG i,j;

	LSA_UNICODE_STRING servicePriv;

	wsprintf(uname,"%s\\%s",szDomain,szUser);

	if (LookupAccountName(NULL,
						  uname,
						  NULL,
						  &sidsize,
						  refdom,
						  &refsize,
						  &peUse))
	{
		wcsncpy(errbuf, __("The account name specified could not be found."), errsize);
		return FALSE;
	}
	if (GetLastError() != 122)
	{
		_snwprintf(errbuf, errsize, __("Internal account lookup failure: %s"), lasterror_string());
		return FALSE;
	}
	sid = malloc(sidsize);
	refsize = sizeof(refdom);
	if (!sid)
	{
		wcsncpy(errbuf, L"Failed to allocate memory to hold a SID.", errsize);
		return FALSE;
	}

	if (!LookupAccountName(NULL,
						  uname,
						  sid,
						  &sidsize,
						  refdom,
						  &refsize,
						  &peUse))
	{
		_snwprintf(errbuf,errsize,__("Failed to lookup account SID: %s"), lasterror_string());
		free(sid);
		return FALSE;
	}

	ZeroMemory(&ObjectAttributes,sizeof(ObjectAttributes));
	status = LsaOpenPolicy(NULL, &ObjectAttributes, POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION | POLICY_WRITE, &PolicyHandle);

	if (status != STATUS_SUCCESS)
	{
		_snwprintf(errbuf, errsize, __("Failed to open local computer policy. Unable to determine user account rights (%i)"),(int)LsaNtStatusToWinError(status));
		free(sid);
		return TRUE;
	}

	status = LsaEnumerateAccountRights(PolicyHandle, sid, &rightList, &rightCount);
	if (status != STATUS_SUCCESS && LsaNtStatusToWinError(status) != 2)
	{
		_snwprintf(errbuf, errsize, __("Failed to enumerate account rights. Unable to determine user account rights (%i)"),(int)LsaNtStatusToWinError(status));
		LsaClose(PolicyHandle);
		free(sid);
		return TRUE;
	}

	if (status == STATUS_SUCCESS)
	{
		/* Found one or more privs. Error 2 = no privs on this account */
		for (i = 0; i < rightCount; i++)
		{
			char privname[256];
			wsprintf(privname,"%S",rightList[i].Buffer);

			for (j=0; j < sizeof(_privileges)/sizeof(_privileges[0]); j++)
				if (!strcmp(privname, _privileges[j].privname))
					_privileges[j].has=TRUE;
		}
	}

	LsaFreeMemory(rightList);

	CheckDefaultPrivileges(PolicyHandle);

	for (j=0; j < sizeof(_privileges)/sizeof(_privileges[0]); j++)
	{
		if (!_privileges[j].has)
		{

			if (!noprompt)
			{
				if (MsgBox(MB_YESNO | MB_ICONQUESTION, __("Account error"), __("The selected account does not have the '%s' right. Do you want to grant the account this right?"),_privileges[j].friendlyname) == IDNO)
				{
					/* Don't create. Ah, well. */
					LsaClose(PolicyHandle);
					free(sid);
					return FALSE;
				}
			}

			/* Grant this right */
			InitLsaString(&servicePriv, _privileges[j].privname);

			status = LsaAddAccountRights(PolicyHandle, sid, &servicePriv, 1);
			if (status != STATUS_SUCCESS)
			{
				_snwprintf(errbuf, errsize, __("Failed to grant account right. (%i)"),(int)LsaNtStatusToWinError(status));
				LsaClose(PolicyHandle);
				free(sid);
				return FALSE;
			}

			if (!noprompt)
				MsgBox(MB_ICONINFORMATION,__("Success"),__("Successfully granted the '%s' right."),_privileges[j].friendlyname);
		}
	}

	LsaClose(PolicyHandle);
	free(sid);

	return TRUE;
}

